/**
 * 
 */
package br.ufpr.inf.jobtuner.pso.mod;

import java.util.ArrayList;
import java.util.Random;

/**
 * @author Evaristo
 * 
 * Class used to implement the PSO algorithm
 * 
 */
public class PSOptimizer {
	ArrayList<PSOParticle> particles = new ArrayList<PSOParticle>();
//	//Not used yet, but needed when implements the stabilization of algorithm 
//	private int globalBestStabilizedCounter = 0; 
	private PSOParticle globalBest = null;
	static final Random random = new Random();
	
//	private static final String[] dimLabel = {"x", "y"};
	//Different tests of dimensionality 
	private static final double[][] boundsMinMax = {{0, 2},{0, 2}/*,{-2, 2},{-2, 2}*/};
	
	private static final double c1 = 0.9;
	private static final double c2 = 1.1;

	/**
	 * Execute the PSO algorithm
	 * @param maxIterations
	 * @param numberOfParticles
	 */
	public void execute(int maxIterations, int numberOfParticles) {
		//Not implemented yet stop by stabilization
		boolean stop = false; 
		initializeParticles(numberOfParticles);
		
		int iteration = 0;
		do {
			calculateAllFitness();
			updatePersonalBest();
			updateGlobalBest();
			moveParticles();
			showProgress(iteration);
			
			/* updateGlobalBest return true 
			 * if Best value has been stabilized*/
			if (stop) {
				System.out.println(" *** Stop! Best fitness has been stabilized.");
				iteration = maxIterations;
			}
		} while (iteration++ < maxIterations);
		showFinalResult();
	}

	/**
	 * Initialize Particles to PSO algorithm
	 * @param particlesListSize
	 */
	private void initializeParticles(int particlesListSize) {
		for (int i = 0; i<particlesListSize; i++) {
			PSOParticle particle = new PSOParticle(boundsMinMax.length);
			double[] position = particle.getPosition();
			double[] velocity = particle.getVelocity();
			
			//Generate the Random Values
			for(int j=0; j<boundsMinMax.length; j++){
				position[j] = boundsMinMax[j][0] + 
						(boundsMinMax[j][1] - boundsMinMax[j][0]) *
						random.nextDouble();
				velocity[j] = boundsMinMax[j][0] + 
						(boundsMinMax[j][1] - boundsMinMax[j][0]) *
						random.nextDouble();
			}
			particles.add(particle);
		}
	}

	/**
	 * Move particles according to PSO formula
	 */
	private void moveParticles() {
		double[] positionGlobalBest = globalBest.getPersonalBestPosition();		
		for(PSOParticle particle : particles){
			double[] velocity = particle.getVelocity();
			double[] position = particle.getPosition();
			double[] positionPersonalBest = particle.getVelocity();
			
			for(int i=0;i<particle.getVelocity().length;i++){
				double phi1 = random.nextDouble(),
						phi2 = random.nextDouble(),
						inertia = random.nextDouble();
				
				//Application of PSO formula
				velocity[i] = inertia*velocity[i] + 
						c1*phi1*(positionPersonalBest[i] - position[i]) + 
						c2*phi2*(positionGlobalBest[i] - position[i]);
				velocity[i] = 
						(velocity[i] > boundsMinMax[i][1]) ? boundsMinMax[i][1] : 
						((velocity[i] < boundsMinMax[i][0]) ? 
								boundsMinMax[i][0] : velocity[i]);
						
				position[i] = velocity[i] + position[i];
			}
		}
	}

	/**
	 * Update Global Best Particle 
	 */
	private void updateGlobalBest() {
		for(PSOParticle particle : particles){
			if(globalBest == null || 
					globalBest.getPersonalBestFitness() < 
					particle.getPersonalBestFitness())
				globalBest = particle;
		}
	}

	/**
	 * Update Personal Best Particle
	 */
	private void updatePersonalBest() {
		for(PSOParticle particle : particles)
			particle.updatePersonalBest();
	}

	/**
	 * Calculate fitness to all particles
	 */
	private void calculateAllFitness() {
		for(PSOParticle particle : particles)
			particle.caculateFitness();					
	}

	/**
	 * Print the progress of PSO algorithm
	 * @param iteration
	 */
	private void showProgress(int iteration) {
		System.out.println("Global Best: " +  
				globalBest.getPersonalBestFitness()  + " " +
				printVectorAsString(globalBest.getPersonalBestPosition()) + " at " +
				iteration);
		
		for(PSOParticle particle : particles){
			System.out.println(iteration + " " + "( " + 
					printVectorAsString(particle.getPosition()) + " ) = " + 
					particle.getFitness());
		}
	}
	
	/**
	 * Print the final Result
	 */
	private void showFinalResult() {
		System.out.println("Global Best: " +
				printVectorAsString(globalBest.getPersonalBestPosition()) + " = " +
				globalBest.getPersonalBestFitness());
	}
	
	/**
	 * Method used to convert an arbitrary double array to String concatenated
	 * @param vector
	 * @return String concatenated of array
	 */
	private String printVectorAsString(double[] vector){
		StringBuffer stringBuffer = new StringBuffer("[");
		for(double d : vector)
			stringBuffer.append(d).append(",");		
		stringBuffer.replace(stringBuffer.length()-1, stringBuffer.length(), "]");
		return stringBuffer.toString();
	}
}